/*
 * anima/space.cc - Generic address space functions.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#include HEADER (anima,space.hh)
#include HEADER (anima,thread.hh)
#include HEADER (anima,mm.hh)
#include HEADER (anima,init.hh)

START_NAMESPACE (anima)

space_t *
space_t::create ()
{
  space_t *space = (space_t *) mm::map (mm::CT_SPACE);

  if (!space)
    return NULL;

  new (space) space_t ();

  space->free_lvl1e = NULL;
  space->used_lvl1e = NULL;
  space->reserved_lvl1e = NULL;
  space->specials_lvl1e = NULL;
  space->shared_lvl1e = NULL;

  space->threads = NULL;

  if (!space->init_instance ())
    {
      if (init::done_p ())
        {
        }
      else
        if (space->used_lvl1e)
          {
            pdb::lvl1e_t *lvl1e = space->used_lvl1e;

            do
              {
                pdb::lvl1e_t *next = lvl1e->next;
                pdb::deallocate (lvl1e);
                lvl1e = next;
              }
            while (lvl1e != space->used_lvl1e);
          }

      mm::unmap ((u64_t) space);
      return NULL;
    }

  mm::activate (space);

  return space;
}

void
space_t::add (thread_t *thread)
{
  if (!threads)
    {
      thread->prev_space = thread;
      thread->next_space = thread;
      threads = thread;
    }
  else
    {
      thread->prev_space = threads->prev_space;
      thread->next_space = threads;
      thread->prev_space->next_space = thread;
      thread->next_space->prev_space = thread;
    }
}

void
space_t::accept (pdb::lvl1e_t *lvl1e)
{
  if (!free_lvl1e)
    {
      lvl1e->prev = lvl1e;
      lvl1e->next = lvl1e;
      free_lvl1e = lvl1e;
    }
  else
    {
      lvl1e->prev = free_lvl1e->prev;
      lvl1e->next = free_lvl1e;
      lvl1e->prev->next = lvl1e;
      lvl1e->next->prev = lvl1e;
    }

  lvl1e->owner.set (this);
  lvl1e->entry.set_usage (pdb::entry_t::USAGE_NONE);
}

void
space_t::adopt (pdb::lvl1e_t *lvl1e)
{
  if (!used_lvl1e)
    {
      lvl1e->prev = lvl1e;
      lvl1e->next = lvl1e;
      used_lvl1e = lvl1e;
    }
  else
    {
      lvl1e->prev = used_lvl1e->prev;
      lvl1e->next = used_lvl1e;
      lvl1e->prev->next = lvl1e;
      lvl1e->next->prev = lvl1e;
    }

  lvl1e->owner.set (this);
  lvl1e->entry.set_usage (pdb::entry_t::USAGE_USER);
}

pdb::lvl1e_t *
space_t::allocate (pdb::entry_t::usage_t usage)
{
  pdb::lvl1e_t *lvl1e = NULL;

  if (init::get_main_space ().null_p ())
    {
      lvl1e = pdb::allocate ();

      if (!lvl1e)
        return NULL;

      lvl1e->owner.set (this);
    }
  else
    {
      if (!free_lvl1e)
        return NULL;

      lvl1e = free_lvl1e;

      lvl1e->prev->next = lvl1e->next;
      lvl1e->next->prev = lvl1e->prev;
      
      if (free_lvl1e == free_lvl1e->next)
        free_lvl1e = NULL;
      else
        free_lvl1e = free_lvl1e->next;
    }

  lvl1e->entry.set_usage (usage);

  if (usage == pdb::entry_t::USAGE_KERNEL)
    {
      lvl1e->prev = NULL;
      lvl1e->next = NULL;
    }
  else
    {
      if (!used_lvl1e)
        {
          lvl1e->prev = lvl1e;
          lvl1e->next = lvl1e;
          used_lvl1e = lvl1e;
        }
      else
        {
          lvl1e->prev = used_lvl1e->prev;
          lvl1e->next = used_lvl1e;
          lvl1e->prev->next = lvl1e;
          lvl1e->next->prev = lvl1e;
        }
    }

  return lvl1e;
}

void
space_t::deallocate (pdb::lvl1e_t *lvl1e)
{
  if (lvl1e->entry.usage != pdb::entry_t::USAGE_KERNEL)
    {
      lvl1e->prev->next = lvl1e->next;
      lvl1e->next->prev = lvl1e->prev;

      if (lvl1e == used_lvl1e)
        {
          if (used_lvl1e == used_lvl1e->next)
            used_lvl1e = NULL;
          else
            used_lvl1e = used_lvl1e->next;
        }
    }

  if (!free_lvl1e)
    {
      lvl1e->prev = lvl1e;
      lvl1e->next = lvl1e;
      free_lvl1e = lvl1e;
    }
  else
    {
      lvl1e->prev = free_lvl1e->prev;
      lvl1e->next = free_lvl1e;
      lvl1e->prev->next = lvl1e;
      lvl1e->next->prev = lvl1e;
    }

  lvl1e->entry.set_usage (pdb::entry_t::USAGE_NONE);
}

bool
space_t::map_range (u64_t start, u64_t end, mm::tte_t::flag_t flags)
{
  for (u64_t p = CUT (start, mm::PAGE_SIZE); p < end; p += mm::PAGE_SIZE)
    if (!map (p, flags))
      {
        for (u64_t q = CUT (start, mm::PAGE_SIZE); q < p; q += mm::PAGE_SIZE)
          unmap (q);
        return false;
      }

  return true;
}

bool
space_t::map_range (u64_t paddr, u64_t start, u64_t end,
                    mm::tte_t::flag_t flags)
{
  for (u64_t p = CUT (start, mm::PAGE_SIZE), pp = CUT (paddr, mm::PAGE_SIZE);
       p < end; p += mm::PAGE_SIZE, pp += mm::PAGE_SIZE)
    if (!map (pp, p, flags))
      {
        for (u64_t q = CUT (start, mm::PAGE_SIZE); q < p; q += mm::PAGE_SIZE)
          unmap (q);
        return false;
      }

  return true;
}

END_NAMESPACE

